module asyn_fifo #(
        parameter ASIZE = 4 ,
        parameter DSIZE = 8 
        )(
input                   I_wrst    ,
input                   I_wclk    ,
input                   I_winc    ,
input      [DSIZE-1:0]  I_wdata   ,
output reg              O_wfull   ,
input                   I_rrst    ,
input                   I_rclk    ,
input                   I_rinc    ,
output reg [DSIZE-1:0]  O_rdata   ,
output reg              O_rempty    
);
wire    [ASIZE-1:0]    S_waddr       ;
reg     [ASIZE  :0]    S_wptr        ;
wire    [ASIZE-1:0]    S_raddr       ;
reg     [ASIZE  :0]    S_rptr        ;
reg  [ASIZE  :0] S_wbin        ;
wire [ASIZE  :0] S_wgraynext   ; 
wire [ASIZE  :0] S_wbinnext    ; 
wire             S_wfull_val   ;
reg  [ASIZE  :0] S_rq1_wptr = 0;
reg  [ASIZE  :0] S_rq2_wptr = 0;
reg  [ASIZE  :0] S_wq1_rptr = 0;
reg  [ASIZE  :0] S_wq2_rptr = 0;


parameter S_depth  = C_FIFO_DEPTH                  ; // define async fifo depth
parameter S_depth_2POWER = {1'b1,{(ASIZE-1){1'b0}} ; // next power of the async fifo depth
S_depth_sub = (S_depth_2POWER - S_depth)           ; // calculate depth difference

// wptr (gray code) from wclk domain to rclk domain 
always @(posedge I_rclk)begin
    if(I_rrst)begin
        S_rq1_wptr <= 0      ;
    end
    else begin
        S_rq1_wptr <= S_wptr ; 
    end
end

S_wbin_l1  = S_wbin > S_depth ? S_wbin + S_depth_sub : S_wbin  ; // write:compare S_wbin with depth
S_wbinnext = S_wbin_l1 + (I_winc & ~O_wfull)                   ; // write:next clock for S_wbin   
S_wgraynext= (S_wbinnext >> 1) ^ S_wbinnext                    ; // write:bin code convert to gray code 
S_rbin_l1  = S_rbin  > S_depth ? S_rbin + S_depth_sub : S_rbin ; // read: compare S_rbin with depth
S_rbinnext = S_rbin_l1 + (I_rinc & ~O_rempty)                  ; // read: next clock for S_rbin
S_rgraynext = (S_rbinnext >> 1) ^ S_rbinnext                   ; // read: bin code convert to gray code caculate

// Memory read-address pointer (okay to use binary to address memory)
assign S_raddr = S_rbin[ASIZE-1:0];
// rbin add rinc when no empty
wire  [ASIZE  :0] S_rbin_l1  = S_rbin  > S_depth ? S_rbin + S_depth_sub : S_rbin;
assign S_rbinnext = S_rbin_l1 + (I_rinc & ~O_rempty);
// Bin to Gray convert 
assign S_rgraynext = (S_rbinnext >> 1) ^ S_rbinnext;// bin to gray code

assign S_wfull_val = (S_wgraynext[ASIZE]^S_wq2_rptr[ASIZE]) &&
                     (S_wgraynext[ASIZE-1]^S_wq2_rptr[ASIZE-1]) &&
                     (S_wgraynext[ASIZE-2:0] == S_wq2_rptr[ASIZE-2:0]);
assign S_waddr = S_wbin[ASIZE-1:0];
// Write full 
always @(posedge I_wclk)begin
    if(I_wrst)begin
        O_wfull <= 1'b0;
    end
    else begin
        O_wfull <= S_wfull_val;
    end
end
// mem assign 
localparam DEPTH = 1 << ASIZE;
(* ram_style="block" *)reg [DSIZE-1:0] mem [0:DEPTH-1];
// write_process
always @(posedge I_wclk)begin
    if(I_winc && (!O_wfull))begin
        mem[S_waddr] <= I_wdata;
    end
end
always @(posedge I_rclk)begin
    O_rdata <= mem[S_raddr];
end
reg  [ASIZE  :0] S_rbin        ;
wire [ASIZE  :0] S_rgraynext   ; 
wire [ASIZE  :0] S_rbinnext    ; 
wire             S_rempty_val  ;
// GRAYSTYLE2 pointer
always @(posedge I_rclk)begin
    if(I_rrst)begin
        S_rbin <= 0             ;
        S_rptr <= 0             ;
    end
    else begin
        S_rbin <= S_rbinnext    ;
        S_rptr <= S_rgraynext   ;
    end
end

// FIFO empty when the next rptr ==  synchronized wptr or on reset
assign S_rempty_val = (S_rgraynext == S_rq2_wptr);
always @(posedge I_rclk)begin
    if(I_rrst)begin
        O_rempty <= 1'b1       ;
    end
    else begin
        O_rempty <= S_rempty_val ; 
    end
end

always @(posedge I_rclk)begin
    S_rq2_wptr <= S_rq1_wptr    ;    // Second pipe just to decrease the meta state .
end
////////////////////////////////////////////////////////////////////////////////////////////////////
//        u0_sync_r2w                                                                             
////////////////////////////////////////////////////////////////////////////////////////////////////
always @(posedge I_wclk)begin
    if(I_wrst)begin
        S_wq1_rptr <= 1'b0          ;
    end
    else begin
        S_wq1_rptr <= S_rptr        ; 
    end
end
always @(posedge I_wclk)begin
    S_wq2_rptr <= S_wq1_rptr    ; 
end

endmodule

